package org.jackysoft.data;


import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.Part;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.shiro.SecurityUtils;
import org.bson.types.ObjectId;
import org.jackysoft.config.ContextConfig;
import org.jackysoft.entity.base.BaseOption;
import org.jackysoft.entity.nosql.AbstractNoSqlEntity;
import org.jackysoft.entity.nosql.NameValuePair;
import org.jackysoft.query.Order;
import org.jackysoft.query.RunnerQuery;
import org.jackysoft.util.FileDownload;
import org.jackysoft.util.IOUtils;
import org.jackysoft.util.MorphiaQueryBuilder;
import org.jackysoft.util.Office2PDF;
import org.jackysoft.util.Page;
import org.jackysoft.util.ReflectionUtil;
import org.jackysoft.util.StringUtils;
import org.jackysoft.ztree.ZTreeNode;
import org.joda.time.DateTime;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.Morphia;
import org.mongodb.morphia.mapping.Mapper;
import org.mongodb.morphia.query.Query;
import org.mongodb.morphia.query.UpdateOperations;
import org.mongodb.morphia.query.UpdateResults;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataAccessException;

import com.google.common.collect.Lists;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.Mongo;


public abstract class AbstractMongodbDataProvider<T extends AbstractNoSqlEntity> implements MongodbDataProvider<T> {

	/**
	 * 
	 */
	private static final Log logger = LogFactory.getLog(AbstractMongodbDataProvider.class);
	public @Value("${mongo.dbname}")
	String database;
	protected Class<T> type;
	protected Datastore ds;
	protected Morphia morphia;
	protected Mongo mongo;
	@Resource 
	protected ContextConfig config;
	
	public AbstractMongodbDataProvider() {		
		this.type = (Class < T > ) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[ 0 ];
	}
	
	
	@PostConstruct
	public void inti(){	
		
		morphia = new Morphia();
		morphia.mapPackage("org.jackysoft.entity.nosql",true);	
		morphia.map(type);
		ds = morphia.createDatastore(mongo,database);
			
	}
	
	@Resource 
	public void setMongo(Mongo mongo){
		 this.mongo = mongo;
	}
	
	public T find(String id) {
	
	    T t = ds.find(type,"_id",new ObjectId(id)).get();
	    return t;	
	
	}

	public List<T> findAll() {

		return ds.find(type).asList();
	}

	
	public List<T> findAll(int start, int size) {
		Query<T> q = ds.find(type);
        q.offset(start);
        q.limit(size);
		return q.asList();
	}

	@Override
	public void remove(T obj) {		
		ds.delete(obj);		
	}

	public void save(T obj) {
		
        ds.save(obj);

	}

	protected void batchSave(Collection<T> objs) {
		ds.save(objs);
	}

	@Override
	public void update(T obj) {
		DBObject t = morphia.toDBObject(obj);
		UpdateOperations<T> ops = ds.createUpdateOperations(type);
		for(String key:t.keySet()){
			if("className".equals(key)||"_id".equals(key))continue;
			ops.setOnInsert(key, t.get(key));			
		}
		UpdateResults<T> rst = ds.update(ds.createQuery(type).filter(Mapper.ID_KEY, obj.ObjectId()),ops);
		int rnum =rst.getUpdatedCount();
		
        logger.info(rnum+" rows was updated");
	}

	@Override
	public void update(String id, Map<String,Object> opvs) {
		if(opvs==null)return;		
		UpdateOperations<T> ops = ds.createUpdateOperations(type);
		for(String key:opvs.keySet()){
			if(ReflectionUtil.isFieldDeclared(type, key)){				
				ops.set(key, opvs.get(key));		    
			}
			
		}
		UpdateResults<T> rst = ds.update(ds.createQuery(type).filter(Mapper.ID_KEY, new ObjectId(id)),ops);
		int rnum =rst.getUpdatedCount();
		
        logger.info(rnum+" rows was updated");
		
	}


	@Override
	public void remove(Collection<T> bs) throws DataAccessException {
		
	    Collection<ObjectId> idz = Lists.newArrayList();
	    for(T t :bs) idz.add(t.ObjectId());
	    ds.delete(type,idz);
	}

	@Override
	public void update(Collection<T> bs) throws DataAccessException {
		for (T o : bs) {
			this.update(o);
		}
	}

	@Override
	public void saveOrUpdate(T b) throws DataAccessException {
		Query<T> q = ds.createQuery(type);
		
		MorphiaQueryBuilder.build(b, q);
		
	    Map<String,Object> sets  =	MorphiaQueryBuilder.buildUpdate(b);
		T t = q.get();
	    if(t==null){
	    	ds.save(b);
	    }else{
	    	UpdateOperations<T> upts = ds.createUpdateOperations(type);
	        for(String name:sets.keySet()){
	        	upts.set(name, sets.get(name));
	        }
	        ds.update(q, upts);
	    }
	}

	@Override
	public int count() throws DataAccessException {
		return 0;
	}
	

	@Override
	public Page query(RunnerQuery mqc, int start, int rows)
			throws DataAccessException {
		if(mqc==null)mqc = new RunnerQuery();
		if(rows<=0)rows=10;
		int offset = start*rows;
		Query<T> q = mqc.mongoQuery(ds,type).offset(offset).limit(rows);
		for(Order odr : mqc.getOrders()){
	    	q.order(odr.morphiaStyle());
	    }	    	    
	    long len = ds.getCount(q);
	    List<T> datas = q.asList();
		Page page = new Page();
		page.setPage(start);
		page.setRecords(Long.valueOf(len).intValue());
		page.setRows(datas);
		return page;
	}
		

	void fillQuery(Query<T> query,DBObject... qs){
		
		if(qs==null|| qs.length==0)return;
		DBObject q = qs[0];
	    for(String key:q.keySet()){
	    	query.filter(key, q.get(key));
	    }
	    if(qs.length>1){
	    	DBObject sort  = qs[1];
	    	 for(String key:sort.keySet()){
	 	    	if("-1".equals(sort.get(key))){
	 	    		query.order("-"+key);
	 	    	}else{
	 	    		query.order(key);
	 	    	}
	 	    		
	 	    }
	    }
	    
	}
	
	Query<T> query(){
		return ds.createQuery(type);
	}
	

	@Override
	public void remove(String id) throws DataAccessException {
		
		ds.delete(ds.createQuery(type).filter(Mapper.ID_KEY, new ObjectId(id)));		
	}

	@Override
	public void removeAll() throws DataAccessException {
		ds.delete(ds.createQuery(type));
	}

	DBObject parseSearchMap(Map<String, Object> smap) {

		DBObject obj = new BasicDBObject();
		if (smap == null)
			return obj;
		smap.remove("operation");

		for (Map.Entry<String, Object> sentry : smap.entrySet()) {
			String[] vs = (String[]) sentry.getValue();
			obj.put(sentry.getKey(), vs[0]);
		}
		return obj;
	}	
	

	protected T createup(Part file, T bean) throws Exception{
		String filename = file.getSubmittedFileName();
		String name = StringUtils.splitPreFileName(filename);
		String ext  = StringUtils.splitPostFileName(filename);
		String relativeUrl = "/attachment/";
		String uname = UUID.randomUUID().toString()+"."+ext;
		T tbean = this.type.newInstance();
		BeanUtils.copyProperties(tbean, bean);
		if("doc".equalsIgnoreCase(ext)){					
			
			Office2PDF wpf = new Office2PDF(config.pdfServer,
				config.pdfServerPort, ext, file.getInputStream());
			ExecutorService executor = Executors.newSingleThreadExecutor();
			Future<String> future = executor.submit(wpf);
			uname = future.get() + ".pdf";
			executor.shutdown();
		}else{
			String appBase = System.getProperty("appBase");
			String target  = String.format("%s%sattachment%s%s",  appBase,File.separator,File.separator,uname);
			IOUtils.copy(file.getInputStream(), new FileOutputStream(target));			
		}
			
		
		relativeUrl += uname;
		
		Object user  = SecurityUtils.getSubject();		
		NameValuePair owner=  new NameValuePair();
		owner.setName((String) PropertyUtils.getProperty(user, "username")); 
		owner.setValue((String) PropertyUtils.getProperty(user, "localName")); 		
		PropertyUtils.setProperty(tbean, "relativePath", relativeUrl);
		PropertyUtils.setProperty(tbean, "name", name);
		PropertyUtils.setProperty(tbean, "dueTime", DateTime.now().getMillis());
		PropertyUtils.setProperty(tbean, "owner", owner);
		return tbean;
		
	}


	@Override
	public void save(Collection<T> ts) {
	    ds.save(ts);
		
	}


	@Override
	public T queryOne(RunnerQuery query) {
		// TODO Auto-generated method stub
		return null;
	}


	@Override
	public Page query(RunnerQuery query, Order sort, int start, int size) {
		// TODO Auto-generated method stub
		return null;
	}


	@Override
	public Page query(RunnerQuery query) {
		// TODO Auto-generated method stub
		return null;
	}


	@Override
	public Page pager(int start, int rows) {
		// TODO Auto-generated method stub
		return null;
	}


	@Override
	public int count(RunnerQuery qc) {
		// TODO Auto-generated method stub
		return 0;
	}


	@Override
	public void upload(Part file, T bean) {
		// TODO Auto-generated method stub
		
	}


	@Override
	public void uploads(Part[] files, List<T> beans) {
		// TODO Auto-generated method stub
		
	}


	@Override
	public FileDownload download(String id) {
		// TODO Auto-generated method stub
		return null;
	}


	@Override
	public List<ZTreeNode> ztree(String id) {
		// TODO Auto-generated method stub
		return null;
	}


	@Override
	public Collection<BaseOption> getOptions(RunnerQuery query) {
		// TODO Auto-generated method stub
		return null;
	}


	@Override
	public void doOrder(Collection<T> beans) {
		// TODO Auto-generated method stub
		
	}	
	
	@Override
	public void update(long id, Map<String, Object> ps) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void update(int id, Map<String, Object> ps) {
		// TODO Auto-generated method stub
		
	}


	@Override
	public List<T> queryAll(RunnerQuery query) {
		// TODO Auto-generated method stub
		return null;
	}
	
	
	
	
}